package operations;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import util.Randomness;

import model.CUT;
import model.Filter;
import model.StatementKind;
import model.TestClusterBuilder;
import model.MethodDeclared;
public class InvokeMethod extends Operation{
	private  String _return;
	private final String _this;
	private final StatementKind method;
	private final List<Object> values;
	private List<Class<?>> replacementObjectParam;
	public InvokeMethod(String _return, String _this, StatementKind method, List<Object> values,List<Class<?>> replacementObjectParam) {
		this._return = _return;
		this._this = _this;
		this.method = method;
		this.values = values;
		this.replacementObjectParam=replacementObjectParam;
	}	
	public static Operation randomlyGenerateMethod(CUT cut, TestClusterBuilder TestCluster){
//		if(clazz.getClazz().getName().equals("java.util.Locale"))
//				System.out.println("InvokeMethod.generate: "+cut.getClazz() );
		//List<Operation> sequence=new LinkedList<Operation>();
		try{
		Class<?> clz=cut.getClazz();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		if(cut.getMethods().length==0) return null;
	   
		List<Class<?>> replacementObjectParam= new LinkedList<Class<?>>();

		StatementKind methodToTest = cut.getMethods()[Randomness.nextRandomInt(cut.getMethods().length)];
//		System.err.println("InvokeMethod.randomlyGenerateMethod: "+ cut.getClazz().getName() + "   "+methodToTest);
//		if(methodToTest.toString().contains("org.jdom2.util.IteratorIterable"))
//			System.err.println("InvokeMethod.randomlyGenerateMethod: "+ cut.getClazz().getName() + "   "+methodToTest);
		
	//	List<Object> values=Operation.selectInputs(methodToTest,TestCluster);
		List<Object> values=new LinkedList<Object>();//
		   List<Class<?>> inputClasses = methodToTest.getInputTypes();
		   for (int i = 0; i < inputClasses.size(); i++) {
			      Class<?> paramTpe = inputClasses.get(i);
					if (Filter.isPrimitiveOrStringType(paramTpe))
						values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
					else{
						if(Filter.isObjectType(paramTpe)){
							paramTpe=generateReference(paramTpe, TestCluster);
							if (Filter.isPrimitiveOrStringType(paramTpe))
								values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
							else{
								if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(paramTpe==null))
									values.add(null);
								else
									values.add(Operation.getReference(paramTpe.getName(), refFactory));
							}
						}
						else{
							paramTpe=generateReference(paramTpe, TestCluster);
							if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL)||(paramTpe==null))
								values.add(null);
							else
								values.add(Operation.getReference(paramTpe.getName(), refFactory));
						}
					}
					replacementObjectParam.add(paramTpe);

		   }
		String caller=null;
		Class<?> clazz=cut.getClazz();
		
		if(Filter.isAbstract(clz) || !Filter.isVisible(clz.getModifiers())){
			Class<?> implementClazz=generateReference(clz, TestCluster);
			if(implementClazz==null) return null;
			caller=getReference(implementClazz.getName(),refFactory);
		}
		else
			caller=getReference(cut.getClazz().getName(), refFactory);   
		String _this = (((MethodDeclared) methodToTest).isStatic()) ? clazz.getName() : caller;
		String _return = (((MethodDeclared) methodToTest).isVoid()) ? null : getReference(((MethodDeclared) methodToTest).getMethod().getReturnType().getName(), refFactory);
		Class<?> returnTe=((MethodDeclared) methodToTest).getMethod().getReturnType();
		if(_return==null &&!((MethodDeclared) methodToTest).isVoid()&&(Filter.isAbstract(returnTe) || !Filter.isVisible(returnTe.getModifiers())||Filter.isObjectType(returnTe)))
			if(generateReference(returnTe, TestCluster)!=null)
				_return=getReference(generateReference(returnTe, TestCluster).getName(),refFactory);
			
		if(_return==null&&!((MethodDeclared) methodToTest).isVoid()&&generateReference(returnTe, TestCluster)!=null){
			Class<?> rType=generateReference(((MethodDeclared) methodToTest).getMethod().getReturnType(), TestCluster);
			if(!rType.getName().contains("java."))
			if(!rType.getName().startsWith("sun"))
			if(!rType.getName().contains("$")) 
			if(!Filter.isPrimitiveOrStringType(rType)){
							_return=Operation.getReference(rType.getName(), refFactory);//clz2.toString();	
				}
		}
		Operation op= new InvokeMethod(_return,_this,methodToTest,values,replacementObjectParam);
//		System.err.println("InvokeMethod.randomlyGenerateMethod: "+ op.toString());

		return op;

		}catch (Throwable e) {
			System.err.println("InvokeMethod.randomlyGenerateMethod: "+ cut.getClazz().getName() + " error "+e.getMessage());
			return null;
		}
		//sequence.add( new InvokeMethod(_return,_this,methodToTest,values,replacementObjectParam));
	}
	public static Operation recommendedgenerate(Class<?> clz,String ref,StatementKind methodToTest, TestClusterBuilder TestCluster){
		List<Class<?>> replacementObjectParam= new LinkedList<Class<?>>();
		List<Operation> sequence=new LinkedList<Operation>();
		CUT cut=TestCluster.getCluster().get(clz.getName());
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		if(!(methodToTest instanceof MethodDeclared)) return null;

		List<Object> values=new LinkedList<Object>();//Operation.selectInputs(methodToTest,TestCluster);
		   List<Class<?>> inputClasses = methodToTest.getInputTypes();
		   for (int i = 0; i < inputClasses.size(); i++) {
			      Class<?> paramTpe = inputClasses.get(i);
					if (Filter.isPrimitiveOrStringType(paramTpe))
						values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
					else{
						if(Filter.isObjectType(paramTpe)){
							paramTpe=generateReference(paramTpe, TestCluster);
							if (Filter.isPrimitiveOrStringType(paramTpe))
								values.add(ValuesGenerator.generatePrimitiveValue(paramTpe));
							else{
								if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
									values.add(null);
								else
									values.add(Operation.getReference(paramTpe.getName(), refFactory));
							}
						}
						else{
							paramTpe=generateReference(paramTpe, TestCluster);
							if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
								values.add(null);
							else
								values.add(Operation.getReference(paramTpe.getName(), refFactory));
						}
					}
					replacementObjectParam.add(paramTpe);
		   }
		 Class<?> clazz=cut.getClazz();
		String _this = ref;
		if(ref==null)
			_this = (((MethodDeclared) methodToTest).isStatic()) ? clazz.getName() : getReference(cut.getClazz().getName(), refFactory);
		if(Filter.isAbstract(clazz))
				_this= (String)getReference( generateReference(clazz,TestCluster).getName(),refFactory);

		String _return = (((MethodDeclared) methodToTest).isVoid()) ? null : getReference(((MethodDeclared) methodToTest).getMethod().getReturnType().getName(), refFactory);


		//sequence.add( new InvokeMethod(_return,_this,methodToTest,values,null));
		//sequence.add( new InvokeMethod(_return,_this,methodToTest,values,replacementObjectParam));
		Operation op= new InvokeMethod(_return,_this,methodToTest,values,replacementObjectParam);
		return op;
	}

	public  StatementKind getMethod(){
		return this.method;
	}
	 public void setValues(List<Object> newValues ){

		  this.values.clear();
		  this.values.addAll(newValues);
	 }

	 public List<Object> getValues(){
		 return this.values;
	 }
	 public List<Class<?>> getReplacementObjectParam(){
		 return this.replacementObjectParam;
	 }

	 public String getReturn(){
		 return this._return;
	 }
	 public void  setReturn(String newRef_){
		 this._return=newRef_;
	 }
		public String getThis(){
			 return this._this;	 
		 }

	@Override
	public String toString(){
		List<Class<?>> inputClasses =this.method.getInputTypes();
	     StringBuilder sb = new StringBuilder();
			StatementKind methodToTest =this.method;
	//		System.err.println("InvokeMethod.toString(): "+ this.method.convertString());
	     	if(this._return!=null){
		    	 Class<?> returnTpe = ((MethodDeclared) methodToTest).getOutputType();

	//	     		String rtrnTpe="("+returnTpe.getName()+ ") "+((MethodDeclared) methodToTest).getOutputType().getName();	     		
		    	 String classRetrnTpe=this._return.substring(0,this._return.indexOf('_'));
		    	 sb.append( this._return + " = "+"("+classRetrnTpe+ ") "); 
	     	}
	     	
	    sb.append(this._this + ".");
	     sb.append(((MethodDeclared) methodToTest).getMethodName() +"(");
	     List<Object> values = this.getValues();
	     for(int j=0;j<values.size();j++){
	    	 Class<?> paramTpe = inputClasses.get(j);
	    	 if(Filter.isObjectType(paramTpe))
	    		 sb.append("(Object) "+ ValuesGenerator.toCodeString(this.replacementObjectParam.get(j),values.get(j)));	
	    	 else
	    		 sb.append( ValuesGenerator.toCodeString(this.replacementObjectParam.get(j),values.get(j)));	
    		 
	         if (j < (values.size() - 1))
	           sb.append(",");
	       }

	     sb.append(");");   
	     return sb.toString();

		}
	@Override
	public Operation clone() {
		List<Object> cloneValues = new ArrayList<Object>(this.values);
		List<Class<?>> cloneReplacementObjectParam= new ArrayList<Class<?>>(this.replacementObjectParam);
		final InvokeMethod clone = new InvokeMethod(this._return, this._this, this.method, cloneValues,cloneReplacementObjectParam);
		clone.addInfo(getInfos());
		return clone;
	}

}
